package pers.yefeng.thread;

import pers.yefeng.constant.StreamConstant;
import pers.yefeng.utils.StreamUtil;
import pers.yefeng.vo.StreamParamV0;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.*;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static org.bytedeco.ffmpeg.global.avcodec.av_packet_unref;

/**
 *
 * @date 2021/05/24 14:45:38
 */
@Slf4j
public class FlvMediaThread extends MediaThread {


    public FlvMediaThread(StreamParamV0 streamParamV0) {
        super(streamParamV0);
    }


    /**
     * 输出流，视频输出地址
     */
    private ByteArrayOutputStream flvOutputStream = new ByteArrayOutputStream();

    /**
     * flv header 输出流的头部信息
     */
    private byte[] flvHeader = null;

    /**
     * 最新数据
     */
    private byte[] theLatestData = null;
    /**
     * 最新的时间戳戳
     */
    private long theLatestTimestamp = 0;


    /**
     * 服务开启时间
     */
    long startTime = 0;
    /**
     * http客户端集合
     */
    private final ConcurrentHashMap<String, ServletOutputStream> httpFlvClientMap = new ConcurrentHashMap<>();


    /**
     * 服务重启后，第一次推送数据 firstPushAfterReStart
     */
    private boolean firstPushAfterReStart = false;


    /**
     * 创建转码推流录制器
     *
     * @return b
     */
    protected FlvMediaThread createRecodeRecorder() {
        if (!grabberStatus) {
            return this;
        }
        createATranscoderFlv();
        return this;
    }


    /**
     * flv转码
     */
    private void createATranscoderFlv() {

        if (StringUtils.isBlank(playAddress)) {
            // 生成观看地址
            this.playAddress = StreamUtil.generatePlaybackAddress() + ".flv";
        }
        recorder = new FFmpegFrameRecorder(flvOutputStream, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
        recorder.setFormat(this.videoType);
        // 判断是否支持转复用
        if (this.supportReuse()) {
            try {
                log.info("{} | 启动转复用录制器……", sourceAddress);
                recorder.start(grabber.getFormatContext());
                recorderStatus = true;
                transferFlag = true;
            } catch (FrameRecorder.Exception e) {
                log.error("{} | 启动转复用录制器失败！", sourceAddress, e);
                setProgress(StreamConstant.SERVICE_START_PROGRESS_FAILURE, "启动转复用录制器失败！");
            }
            return;
        }


        // 转码
        log.info("{} | 启动Flv转码录制器……", sourceAddress);
        recorder.setInterleaved(false);
        recorder.setVideoOption("tune", "zerolatency");
        recorder.setVideoOption("preset", "ultrafast");
        recorder.setVideoOption("crf", "26");
        recorder.setVideoOption("threads", "1");
        recorder.setFrameRate(25);// 设置帧率
        recorder.setGopSize(25);// 设置gop,与帧率相同，相当于间隔1秒chan's一个关键帧
//						recorder.setVideoBitrate(500 * 1000);// 码率500kb/s
        recorder.setVideoCodecName("libx264");
//						recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
        recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
//						recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
        recorder.setAudioCodecName("aac");
        try {
            recorder.start();
            recorderStatus = true;
        } catch (FrameRecorder.Exception e) {
            log.error("{} | 创建转码录制器异常！", sourceAddress, e);
            setProgress(StreamConstant.SERVICE_START_PROGRESS_FAILURE, "创建转码录制器失败！");
        }
    }


    /**
     * 开启转流服务
     *
     * @return view
     */
    public void transform() {
        if (!grabberStatus || !recorderStatus) {
            return;
        }
        log.info("{} | 开启转流服务……", sourceAddress);
        try {
            grabber.flush();
        } catch (FrameGrabber.Exception e) {
            log.error("{} | 清空拉流器缓存失败", sourceAddress, e);
        }
        if (flvHeader == null) {
            flvHeader = flvOutputStream.toByteArray();
            flvOutputStream.reset();
        }
        String mapKey = sourceAddress + "-" + videoType;
        setProgress(StreamConstant.SERVICE_START_PROGRESS_SUCCESS, "");
        StreamConstant.MEDIA_INFO_MAP.put(mapKey, this);
        // 服务启动完成
        starting = false;


        //时间戳计算
        int errorCount = 0;
        running = true;
        long videoTS;
        while (running) {
            // 出错次数过多时，重新建立连接或重启
            if (errorCount > 10) {
                if (transferFlag) {
                    // 转码重新建立连接对转复用不生效，所以进行重启服务
                    restartService = true;
                    break;
                }
                try {
                    log.info("{} | 重新建立链接……", sourceAddress);
                    grabber.restart();
                    grabber.flush();
                    // 重新建立建立成功，将错误次数清零
                    errorCount = 0;
                } catch (FrameGrabber.Exception e1) {
                    log.error("{} | 重新建立连接失败", sourceAddress, e1);
                    errorCount++;
                }
            }

            try {

                if (transferFlag) {

                    //转复用
                    AVPacket pkt = grabber.grabPacket();
                    if (null == pkt || pkt.isNull()) {
                        log.error("{} | pkt is null", sourceAddress);
                        errorCount++;
                        continue;
                    }
                    recorder.recordPacket(pkt);
                    av_packet_unref(pkt);
                } else {
                    //转码
                    Frame frame = grabber.grabFrame();
                    if (frame == null) {
                        log.error("{} | frame is null", sourceAddress);
                        errorCount++;
                        continue;
                    }
                    recorder.record(frame);
                }
            } catch (Exception e) {
                log.error("{} | 转流操作异常", sourceAddress, e);
                errorCount++;
            }


            if (flvOutputStream.size() > 0) {
                theLatestData = flvOutputStream.toByteArray();
                // 发送视频到前端
                sendFlvFrameData(theLatestData);
                flvOutputStream.reset();
            }

        }

        // close包含stop和release方法。录制文件必须保证最后执行stop()方法
        try {
            recorder.close();
            grabber.close();
            flvOutputStream.close();
        } catch (IOException e) {
            log.error("{} | 转流操作结束，关闭流异常。", sourceAddress, e);
        }

        // 进行重启
        if (restartService) {
            log.info("{} | 重新启动……", sourceAddress);
            flvOutputStream = new ByteArrayOutputStream();
            restartService = false;
            this.grabberStatus = false;
            this.recorderStatus = false;
            starting = true;
            firstPushAfterReStart = true;
            this.createGrabber().createRecodeRecorder().transform();
        }
        log.info("{} | 结束转流服务。", sourceAddress);
        StreamConstant.MEDIA_INFO_MAP.remove(mapKey);
    }


    private void sendFlvFrameData(byte[] data) {
        Iterator<Map.Entry<String, ServletOutputStream>> iterator = httpFlvClientMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, ServletOutputStream> next = iterator.next();
            ServletOutputStream outputStream = null;
            try {
                outputStream = next.getValue();
                outputStream.write(data, 0, data.length);
                outputStream.flush();
            } catch (java.lang.Exception e) {
                try {
                    if (null != outputStream) {
                        outputStream.close();
                    }
                } catch (IOException ioException) {
                    ioException.printStackTrace();
                }
                httpMap.remove(next.getKey());
                iterator.remove();
                log.error("{} | {} | 推流失败，关闭连接。剩余连接：{}", sourceAddress, next.getKey(), httpMap.size(), e);
            }
        }
        firstPushAfterReStart = false;
        hasClient();
    }


    /**
     * 新增http客戶端
     */
    public void addFlvHttpClient(HttpServletRequest request, HttpServletResponse response) {
        if (running) {
            response.addHeader("Content-Disposition", "attachment;filename=\"" + playAddress + "\"");
            response.setContentType("video/x-flv");
            response.setHeader("Connection", "keep-alive");
            response.setHeader("accept_ranges", "bytes");
            response.setHeader("pragma", "no-cache");
            response.setHeader("cache_control", "no-cache");
            response.setHeader("transfer_encoding", "CHUNKED");
            response.setHeader("SERVER", "yefeng");
            String ipAddress = StreamUtil.getIpAddress(request);


            response.setStatus(200);
            ServletOutputStream outputStream = null;
            try {
                outputStream = response.getOutputStream();
            } catch (IOException e) {
                log.error("{} | {} outputStream获取失败！", sourceAddress, ipAddress, e);
            }
            if (null == outputStream) {
                return;
            }
            try {
                outputStream.write(flvHeader, 0, flvHeader.length);
                outputStream.flush();
            } catch (IOException e) {
                log.error("{} | {} 写入头部数据失败！", sourceAddress, ipAddress, e);
            }

            httpFlvClientMap.put(ipAddress, outputStream);
            httpMap.put(ipAddress, System.currentTimeMillis());
            log.info("{} | {} 新增连接！当前连接数：{}", sourceAddress, ipAddress, httpMap.size());
            while (true) {
                // 线程不释放，保持连接。
                if (null == httpFlvClientMap.get(ipAddress)) {
                    // 当链接被移除后，则结束方法，关闭线程。
                    log.error("{} | {} 结束线程！", sourceAddress, ipAddress);
                    break;
                }
                try {
                    // 线程睡眠，只要线程不结束，就不会断掉http连接
                    // 不知道是否有更好的方式
                    sleep(1000 * 60 * 1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    @Override
    public void getHlsFile(HttpServletRequest request, HttpServletResponse response, String playName) {

    }


    @Override
    public void recordHls(HttpServletRequest request, String playAddress, String playName) {

    }
}